﻿using System;
using System.Text;
using System.Diagnostics;
using System.Collections.Generic;

namespace gKodes.Collections
{
    //Version : 2
    [DebuggerDisplay("{Start} > {End}")]
    public struct Range : IEnumerable<long>, IComparable<Range>
    {
        /// <summary>
        /// Gets a Range structure of 0 length
        /// </summary>
        public static readonly Range Empty = new Range();

        /// <summary>
        /// Used for internally storing starting value of the range
        /// </summary>
        private long mStart;

        /// <summary>
        /// /// Used for internally storing ending value of the range
        /// </summary>
        private long mEnd;

        /// <summary>
        /// Gets the starting value of the range
        /// </summary>
        public long Start
        {
            get { return this.mStart; }
            set { this.mStart = value; }
        }

        /// <summary>
        /// Gets the lenght of the range
        /// </summary>
        public long Length
        {
            get { return this.mEnd - this.mStart; }
            set { this.mEnd = this.mStart + value; }
        }

        /// <summary>
        /// Gets the ending value of the range
        /// </summary>
        public long End
        {
            get { return this.mEnd; }
            set { this.mEnd = value; }
        }

        /// <summary>
        /// Gets a value indicating the difference between the start and end is not 0
        /// </summary>
        public bool IsRange { get { return this.mStart != this.mEnd; } }

        public bool IsNegative { get { return this.mStart < 0 && this.mEnd < 0; } }

        public bool IsInverse { get { return (this.mEnd - this.mStart) < 0; } }

        /// <summary>
        /// Creates an instance of Range Structure
        /// </summary>
        /// <param name="start">Starting value of the range</param>
        /// <param name="end">Ending value of the range</param>
        public Range(long start, long end)
        {
            this.mStart = start;
            this.mEnd = end;
        }

        /// <summary>
        /// Set the Start value the range
        /// </summary>
        /// <param name="value">The value to which the Start of the range is to be set.</param>
        /// <returns>Returns the updated range after changes</returns>
        public Range From(long value)
        {
            return new Range(value, this.mEnd);
        }

        /// <summary>
        /// Set the End value the range.
        /// </summary>
        /// <param name="value">The value to which the End of the range is to be set.</param>
        /// <returns>Returns the updated range after changes</returns>
        public Range To(long value)
        {
            return new Range(this.mStart, value);
        }

        #region .math method's

        #region Intersection mehtod's

        /// <summary>
        /// Get the intersection of this Range and the specified <paramref name="other"/> Range.
        /// </summary>
        /// <param name="other">The range to intersect.</param>
        /// <returns>Intersected range of this and other Ranges</returns>
        public void Intersect(Range other)//Intersect
        {
            this = Range.Intersection(this, other);
        }

        /// <summary>
        /// Determines if this range intersects with.<paramref name="other"/>.
        /// </summary>
        /// <param name="other">The range to test.</param>
        /// <returns></returns>
        public bool IntersectsWith(Range other)
        {
            return (Math.Abs(this.CompareTo(other)) < 6);
        }

        #endregion

        /// <summary>
        /// Get the union of this Range and the specified <paramref name="other"/> Range.
        /// </summary>
        /// <param name="other">The range to form a union with.</param>
        /// <returns>The union of both ranges</returns>
        public void Union(Range other)
        {
            this = Range.Union(this, other);
        }

        /// <summary>
        /// Get the complement of this Range with the specified <paramref name="other"/> Range.
        /// </summary>
        /// <param name="other">The range to complement with.</param>
        /// <returns>The complement range</returns>
        public void Complement(Range other)
        {
            int cresult = this.CompareTo(other);

            if (cresult < 0)
            {
                other = Inverse(other);
                cresult *= -1;
            }
            if (cresult > 1 && cresult < 4)
                this.mStart = other.mEnd;

            throw new ArgumentOutOfRangeException("other",
                "Cannot complement with the given range");
        }

        //SymmetricDifference
        /// <summary>
        /// 
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public IEnumerable<Range> Exclude(Range other)
        {
            if (this.Contains(other))
            {
                yield return new Range(this.mStart, other.mStart - 1);
                yield return new Range(other.mEnd + 1, this.mEnd);
            }
            else { yield return this; }
        }

        #endregion

        #region Contains method's

        /// <summary>
        /// Determines if the specified value is contained within this Range.
        /// </summary>
        /// <param name="value">The value to test.</param>
        /// <returns>This method returns true if the value represented by the 
        /// <paramref name="value"/> parameter is contained within this Range; otherwise false.</returns>
        public bool Contains(long value)
        {
            if (value >= this.mStart && value <= this.mEnd)
                return true;
            return (this.mEnd < this.mStart && value <= this.mStart && value >= this.mEnd);
        }

        /// <summary>
        /// Determines if the specified Range is contained within this Range.
        /// </summary>
        /// <param name="other">The Range to test with.</param>
        /// <returns>This method returns true if the range represented by the 
        /// <paramref name="value"/> parameter is contained within this Range; otherwise false.</returns>
        public bool Contains(Range other)
        {
            return this.Contains(other.mStart) && this.Contains(other.mEnd);
        }

        #endregion

        #region GetEnumerator method's

        #region IEnumerator's

        private struct Enumerator : IEnumerator<long>, IDisposable
        {
            private Range iBelongTo;

            private long mCurrent;

            private long mIncrement;

            public long Current
            {
                get { return this.mCurrent; }
            }

            #region IDispose method's

            public void Dispose() { }

            #endregion

            object System.Collections.IEnumerator.Current
            {
                get { return this.mCurrent; }
            }

            public bool MoveNext()
            {
                if (Current < iBelongTo.End)
                {
                    this.mCurrent += this.mIncrement;
                    return true;
                }
                return false;
            }

            public void Reset()
            {
                this.mCurrent = this.iBelongTo.mStart - this.mIncrement;
            }

            internal Enumerator(Range ibt)
            {
                this.iBelongTo = ibt;
                this.mIncrement = (ibt.IsInverse) ? -1 : 1;
                this.mCurrent = ibt.mStart - this.mIncrement;
            }
        }

        #endregion

        /// <summary>
        /// Returns an enumerator that iterates through the Range.
        /// </summary>
        /// <returns>System.Collections.Generic.IEnumerator<long> that 
        /// can be used to iterate through the Range.</returns>
        public IEnumerator<long> GetEnumerator()
        {
            return new Enumerator(this);
        }

        /// <summary>
        /// Returns an enumerator that iterates through the Range.
        /// </summary>
        /// <returns>System.Collections.Generic.IEnumerator<long> that 
        /// can be used to iterate through the Range.</returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region Overriden mehtod's

        /// <summary>
        /// Returns a string that represents the current object.
        /// </summary>
        /// <returns>A string that represents the current object.</returns>
        public override string ToString()
        {
            return String.Format("{0}, {1}", this.mStart, this.mEnd);
        }

        /// <summary>
        /// Determines whether the specified Object is equal to the current Range Structure.
        /// </summary>
        /// <param name="obj">The object to compare with the current Range Object.</param>
        /// <returns>true if the specified Object is equal to the current Range Object; otherwise, false.</returns>
        public override bool Equals(object obj)
        {
            if (obj is Range)
            {
                return this.Equals((Range)obj);
            }
            return false;
        }

        /// <summary>
        /// Determines whether the specified Range is equal to the current Range.
        /// </summary>
        /// <param name="other">The Range to compare with the current Range.</param>
        /// <returns>true if the specified Range is equal to the current Range; otherwise, false.</returns>
        public bool Equals(Range other)
        {
            return (this.mStart == other.mStart && this.mEnd == other.mEnd);
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>A hash code for the current Range Object.</returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        #endregion

        #region Compare method

        /// <summary>
        /// Compares the current Range with another Range.
        /// </summary>
        /// <param name="other">An Range to compare with this Range.</param>
        /// <returns>-9 to 9</returns>
        public int CompareTo(Range other)
        {
            return Range.Compare(this, other);
        }

        public bool IsToRight(long value)
        {
            return this.IsInverse ^ (value > this.mEnd);
        }

        public bool IsToRight(Range other)
        {
            return this.IsInverse ^ (other.End > this.mEnd && other.Start > this.mEnd);
        }

        public bool IsToLeft(long value)
        {
            return this.IsInverse ^ (value < this.mStart);
        }

        public bool IsToLeft(Range other)
        {
            return this.IsInverse ^ (other.Start < this.mStart && other.End < this.mStart);
        }

        #endregion

        #region Static method's

        public static Range Abs(Range range)
        {
            if (range.mEnd < range.mStart)
                return new Range(range.mEnd, range.mStart);
            return range;
        }

        public static Range Inverse(Range range)
        {
            return new Range(range.mEnd, range.mStart);
        }

        public static int Compare(Range a, Range b)
        {
            if (a.Equals(b) || a.Equals(Abs(b)))
                return 0;

            if (!b.Contains(a))
            {
                int result = 0;
                bool isink = a.IsInverse == b.IsInverse; // Is Inverse synchronised
                long rdiff = Math.Min(a.mEnd - b.mStart, b.mEnd - a.mStart);
                if (a.Contains(b))
                {
                    if (rdiff == 0 || rdiff == b.Length)
                    {
                        result = (b.mStart == a.mStart || b.mEnd == a.mStart) ? 2 : 4;
                        return isink ? result : -result;
                    }
                    return 1;
                }
                else if (a.Contains(b.mEnd)) // Info: 3 = Starts before and ends inside me
                    return (isink ? 3 : -5);
                else if (a.Contains(b.mStart)) // Info: 5 = Starts in me and ends after me
                    return (isink ? 5 : -3);

                result = ((a.IsToLeft(b)) ? 7 : 8) + (Math.Max(rdiff, rdiff - b.Length) == -1 ? -1 : 1);
                return isink ? result : -result;
            }
            return -1;
        }

        public static void Sink(Range a, ref Range b)
        {
            if (a.IsInverse != b.IsInverse)
                b = Inverse(b);
        }

        internal static int SinkCompare(Range a, ref Range b)
        {
            Sink(a, ref b);
            return Range.Compare(a, b);
        }

        public static bool IsAdjacent(Range a, Range b)
        {
            int result = Range.SinkCompare(a, ref b);
            return (result > 5 && result < 8);//(result == 6 || result == 8);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns>The union of both ranges</returns>
        public static Range Union(Range a, Range b)
        {
            Sink(a, ref b);
            return new Range(Math.Min(a.mStart, b.mStart), Math.Max(a.mEnd, b.mEnd));
        }

        public static Range Intersection(Range a, Range b)
        {
            int cresult = Range.SinkCompare(a, ref b);

            if (cresult < 1)
                return a;
            else if (cresult == 3)
                return b.From(a.mStart);
            else if (cresult == 5)
                return b.To(a.mEnd);
            else if (cresult < 5)//2 || cresult == 4
                return b;

            return Range.Empty;
        }

        public static Range Complement(Range a, Range b)
        {
            Sink(a, ref b);
            if (a.mStart < b.mStart)
                return a.To(b.mStart - 1);
            return b.From(a.mStart - 1);
            //return Range.Empty;
        }

        //public static Range Complement(Range a, Range b)
        //{
        //    int cresult = Range.SinkCompare(a, ref b);

        //    //TODO: should work with 1, 2, 3
        //    if (cresult > 1 && cresult < 4)
        //        return a.From(b.mEnd);

        //    return Range.Empty;
        //}

        //TODO: rethink about the following two mehtods
        public static Range Excluded(Range a, Range b)
        {
            Sink(a, ref b);
            return new Range(Math.Min(a.mStart, b.mStart), Math.Min(a.mEnd, b.mEnd));
        }

        public static Range Exclude(Range a, Range b)
        {
            Sink(a, ref b);
            return new Range(Math.Min(a.mEnd, b.mEnd), Math.Max(a.mEnd, b.mEnd));
        }

        #endregion

        //private static bool DiffIsOneChecker(params long[] values)
        //{
        //    for (int i = 0; i < (values.Length - 1); i++)
        //        if (Math.Abs((values[i] - values[i + 1])) == 1)
        //            return true;
        //    return false;
        //}
    }
}
